package org.flint.data.structure.queue;

/**
 * @author flint92
 */
public class LoopQueue<E> implements Queue<E> {

    private static final int DEFAULT_CAPACITY = 1 << 4;

    private E[] data;
    private int front;
    private int tail;
    private int size;

    public LoopQueue() {
        this(DEFAULT_CAPACITY);
    }


    public LoopQueue(int capacity) {
        if (capacity < 0) {
            throw new IllegalArgumentException("capacity must not less than 0.");
        }

        this.data = get(capacity);
        this.front = this.tail = 0;
        this.size = 0;
    }

    @SuppressWarnings("unchecked")
    private E[] get(int capacity) {
        return (E[]) new Object[capacity + 1];
    }

    public int getCapacity() {
        return this.data.length - 1;
    }

    @Override
    public int getSize() {
        return this.size;
    }

    @Override
    public boolean isEmpty() {
        return this.tail == this.front;
    }

    @Override
    public void enqueue(E e) {
        if (isFull()) {
            resize();
        }
        data[tail] = e;
        tail = (tail + 1) % data.length;
        size++;
    }

    @Override
    public E dequeue() {
        if (isEmpty()) {
            throw new IllegalArgumentException("queue is empty.");
        }
        E ret = data[front];
        // help gc
        data[front] = null;
        front = (front + 1) % data.length;
        size--;
        return ret;
    }

    @Override
    public E getFront() {
        if (isEmpty()) {
            throw new IllegalArgumentException("queue is empty.");
        }
        return data[front];
    }

    private boolean isFull() {
        return (tail + 1) % data.length == front;
    }

    private void resize() {
        int resizeCapacity = getCapacity() << 1;
        E[] newData = get(resizeCapacity);

        if (front < tail) {
            System.arraycopy(data, front, newData, 0, size);
        } else {
            int firstCopyLength = this.data.length - front;
            System.arraycopy(data, front, newData, 0, firstCopyLength);
            System.arraycopy(data, 0, newData, firstCopyLength, tail);
        }

        this.data = newData;
        this.front = 0;
        this.tail = size;
    }


}
